package fr.bludwarf.skywarrior.global;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Set;

import fr.bludwarf.skywarrior.evenements.Evenement;
import fr.bludwarf.skywarrior.evenements.EvenementImpossible;
import fr.bludwarf.skywarrior.exceptions.ConstructionImpossibleException;
import fr.bludwarf.skywarrior.exceptions.EvenementInconnuOublieException;
import fr.bludwarf.skywarrior.exceptions.IleInconnueException;
import fr.bludwarf.skywarrior.exceptions.JoueurInconnuException;
import fr.bludwarf.skywarrior.ile.Batiment;
import fr.bludwarf.skywarrior.ile.Construction;
import fr.bludwarf.skywarrior.ile.EtatIle;
import fr.bludwarf.skywarrior.ile.Ile;
import fr.bludwarf.skywarrior.joueur.EtatJoueur;
import fr.bludwarf.skywarrior.joueur.Joueur;
import fr.bludwarf.skywarrior.joueur.Techno;
import fr.bludwarf.skywarrior.stats.Prevision;

public class EtatMonde extends EtatPere<Monde, Joueur, EtatJoueur> {
	
	private Temps _temps;
	
	/** historique des constructions */
	ArrayList<Construction> _constructions = new ArrayList<Construction>();

	// FIXME : pour le debug
	/**
	 * Crée un nouveau monde à l'instant 0
	 * @param debut date et heure réelles du début de la partie
	 */
	public EtatMonde(final Monde monde, final Temps temps) {
		super(monde, null);
		_temps = temps;
		super.init();
	}
	
	/**
	 * Constructeur de recopie
	 * @param etat état de base
	 * @throws Exception 
	 */
	public EtatMonde(final EtatMonde etat) throws Exception {
		super(etat);
		_temps = etat._temps;
		
		_constructions = new ArrayList<Construction>();
		for (final Construction c : etat._constructions)
		{
			_constructions.add(new Construction(c));
		}
	}
	
	public <T> EtatMonde cloneFor(Class<T> clazz) throws Exception
	{
		EtatMonde etatMonde;
		try
		{
			etatMonde = new EtatMonde(this);
		} catch (Exception e)
		{
			throw new Exception("Impossible de clone l'état du monde pour la classe : " + clazz.getSimpleName());
		}
		
		if (clazz == Prevision.class)
		{
			etatMonde._constructions = new ArrayList<Construction>();
		}
		
		return etatMonde;
	}
	
	public Monde getMonde() {
		return _entite;
	}
	
	@Override
	public String toString() {
		final StringBuilder sb = new StringBuilder();
		final double tCycle = prochainCycle().tempsRestant();

		final String formatDateReelle = getTemps().formatDateReelle();
//		sb.append("===== MONDE le " + formatDateReelle + " (t = ")
//			.append(getTemps()).append(") === Prochain cycle : " + Temps.toString(tCycle) + " =====");
		
		for (final EtatJoueur etat : _etatsFils.values())
		{
			sb
//			.append(String.format("%s (t = %s)",
//				formatDateReelle,
//				getTemps()
//			))
			.append(etat);
		}

		return sb.toString();
	}

	/**
	 * @return
	 * @since 17 nov. 2011
	 */
	public Evenement prochainCycle() {
		return getTemps().prochainCycle(this);
	}

	public HashMap<Joueur, EtatJoueur> getEtatsJoueurs() {
		return _etatsFils;
	}
	
	public EtatJoueur getEtatJoueur(final String nomJoueur) throws JoueurInconnuException
	{
		return getEtatJoueur(_entite.getJoueur(nomJoueur));
	}

	public EtatJoueur getEtatJoueur(Joueur joueur) {
		return _etatsFils.get(joueur);
	}

	@Override
	public Evenement prochainEvenement() throws Exception {
//		// event joueur
//		final Evenement pEventIle = Evenement.prochainEvenementDe(_etatsFils.values());		
//		final Evenement prochainCycle = prochainCycle();
//		if (!pEventIle.estPossible())
//		{
//			return prochainCycle;
//		}
//		else
//		{
//			return Evenement.prochainEvenementParmis(
//					prochainCycle, // prochain cycle
//					pEventIle // prochain évènement parmis tous les joueurs
//			);
//		}
		// event joueur
		final Evenement pEventIle = Evenement.prochainEvenementDe(_etatsFils.values());		
		final Evenement prochainCycle = prochainCycle();
		return Evenement.prochainEvenementParmis(
				prochainCycle, // prochain cycle
				pEventIle // prochain évènement parmis tous les joueurs
		);
	}

	@Override
	protected EtatJoueur etatInitialFils(final Joueur joueur) {
		return new EtatJoueur(joueur, this);
	}

	public EtatIle getEtatIle(Ile ile) throws IleInconnueException {
		for (final Joueur joueur : getEtatsJoueurs().keySet())
		{
			final EtatJoueur etatJoueur = getEtatsJoueurs().get(joueur);
			if (etatJoueur.getEtatsIles().containsKey(ile))
			{
				return etatJoueur.getEtatsIles().get(ile);
			}
		}
		throw new IleInconnueException(ile.getNom());
	}
	
	

	/************************************************************************
	 *                         ETAT INITIAL DU MONDE
	 ************************************************************************/
	
	/**
	 * Ajoute un certain nombre de bâtiment sur une ile
	 * @param nb
	 * @param batiment
	 * @param ile
	 * @throws IleInconnueException 
	 */
	public void addBatiment(final int nb, final Batiment batiment, final Ile ile) throws IleInconnueException
	{
		final EtatIle etatIle = getEtatIle(ile);
		for (int i = 0; i < nb; ++i) {
			etatIle.add(batiment);
		}
	}
	
	/**
	 * Quantité de matériaux et d'éther en stock d'un joueur
	 * @param materiaux
	 * @param ether
	 * @param joueur
	 */
	public void setStocks(final int materiaux, final int ether, final Joueur joueur)
	{
		getEtatJoueur(joueur).setStocks(materiaux, ether);
	}
	
	@Override
	public void avancer(double temps) throws Exception {
		_temps = new Temps(_temps, temps);
		super.avancer(temps);
	}

	public void setEvenementDeclencheur(Evenement evenement) {
		_periode = new Periode(evenement.tempsRestant(), evenement, this);
	}
	
	@Override
	public Temps getTemps() {
		return _temps;
	}

	@Override
	protected EtatJoueur newEtatFils(EtatJoueur etatInitial,
			EtatPere<Monde, Joueur, EtatJoueur> etatPere) throws Exception
	{
		return new EtatJoueur(etatInitial, etatPere);
	}

	public Set<Joueur> getJoueurs()
	{
		return getMonde().getJoueurs();
	}

	public Date getDateReelle()
	{
		return getTemps().getDateReelle().getTime();
	}
	
	/**
	 * pour l'historique
	 * @param construction
	 */
	public void add(Construction construction)
	{
		_constructions.add(construction);
	}
	
	/**
	 * @return historique de toutes les constructions terminées
	 */
	public ArrayList<Construction> getConstructions()
	{
		return _constructions;
	}

	public Joueur getJoueurPrincipal() throws JoueurInconnuException
	{
		final Set<Joueur> joueurs = getJoueurs();
		if (joueurs.size() != 1)
		{
			throw new JoueurInconnuException("Impossible de trouver le joueur principal");
		}
		for (final Joueur j : joueurs)
		{
			return j;
		}
		throw new JoueurInconnuException("Impossible de trouver le joueur principal");
	}

	@Override
	public void execute(Evenement e)
	{
		// à implémenter pour le type d'évènement précis
	}
}
